
#include <vector>
#include <cstdint>
#include <memory>
#include <string>

#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <cstdlib>
#include <cstdio>
#include <cstring>
#include <netdb.h>

#include <tenacitas.command/receiver.h>

namespace ten_cmd = tenacitas::command;

namespace tenacitas {
    namespace echo_server {

        struct message_types {
            typedef std::string message;
            typedef std::shared_ptr<message> message_ptr;
        };

        struct app_data_types {
            typedef message_types::message app_data;
            typedef message_types::message_ptr app_data_ptr;
        };
            

        struct connection_types {
                    
            struct connection {
                typedef std::shared_ptr<connection> ptr;

                connection( int p_socket )
                    : m_socket( p_socket ) {}

                connection( ):
                    m_socket( -1 ) {}

                ~connection( ) {
                    if ( m_socket != -1 ) {
                        ::close( m_socket );
                    }
                }
                    

                bool operator==( int p_i ) {
                    return ( p_i == m_socket );
                }

                connection & operator =( int p_i ) {
                    m_socket = p_i;
                    return *this;
                }

                operator int( ) const {
                    return m_socket;
                }

                int socket( ) const {
                    return m_socket;
                }

            private:
                int m_socket;
            };

            typedef connection::ptr connection_ptr;
        };

        struct connector_types {

            /** a socket is a surrogate to an internet connection (IP +
                port ) */		
		
            struct connector {
                typedef connection_types::connection connection;
                typedef connection_types::connection_ptr connection_ptr;


                connector( const std::string & p_service, int p_qlen )
                    : m_listener( ),
                      m_service( p_service ),
                      m_qlen( p_qlen ) { }
                    
                bool start( ) {
                    if ( !m_listener ) {
                        struct servent *pse = nullptr;
                        struct protoent *ppe = nullptr;
                        struct sockaddr_in sin;
                        int type;

                        memset( &sin, 0, sizeof(sin) );

                        sin.sin_family = AF_INET;
                        sin.sin_addr.s_addr = INADDR_ANY;
                        pse = getservbyname( m_service.c_str( ), "tcp" );

                        if ( pse ) { 
                            sin.sin_port = htons(
                                ntohs( ( unsigned short )pse->s_port ) ); 
                        }

                        else if ( ( sin.sin_port =
                                    htons( ( unsigned short )
                                           atoi( m_service.c_str( ) ) ) ) == 0 ) {  
                            return false;
                        }

                        ppe = getprotobyname( "tcp" );
                            
                        if ( ppe == 0 ) {
                            return false;
                        }

                        type = SOCK_STREAM;

                        int s = socket( PF_INET, type, ppe->p_proto );

                        if ( s < 0 ) {
                            return false;
                        }

                        if ( ::bind( s, ( struct sockaddr * )&sin,
                                     sizeof( sin ) ) < 0 ) { 
                            return false;
                        }

                        if ( listen( s, m_qlen ) < 0 ) {
                            return false;
                        }

                        m_listener = connection_ptr( new connection( s ) );
                    }
                    return true;
                }

                connection_ptr connected( ) {
                    unsigned int alen;
                    struct sockaddr_in fsin;
                    alen = sizeof( fsin );
                    int s = accept( *m_listener,
                                    ( struct sockaddr * )&fsin,
                                    &alen ); 
                    if ( s < 0 ) {
                        return connection_ptr( );
                    }
                    return connection_ptr( new connection( s ) );
                }

            private:
                connection_ptr m_listener;
                std::string m_service;
                int m_qlen;
            };


            typedef std::shared_ptr<connector> connector_ptr;

            struct connector_factory {
                connector_factory( const std::string & p_service,
                                   int p_qlen = 32 )
                    : m_service( p_service ),
                      m_qlen ( p_qlen ) { }

                connector_ptr create( ) {
                    return connector_ptr( new connector( m_service, m_qlen ) );
                }

            private:
                std::string m_service;
                int m_qlen;
            };

            typedef std::shared_ptr<connector_factory> connector_factory_ptr;
        };


        struct message_io_types {

            typedef connection_types::connection_ptr connection_ptr;
            typedef message_types::message message;                    
            typedef message_types::message_ptr message_ptr;

            struct message_io {
                    
                message_ptr read( connection_ptr p_con ) {
                    static const int16_t l_buf_size = 2048;
                    
                    char l_buf[l_buf_size];
                    std::memset( l_buf, '\0', l_buf_size );
                    
                    if ( ::read( p_con->socket( ), l_buf, l_buf_size ) <= 0 ) {
                        return message_ptr( );
                    }
                        
                    return message_ptr( new message( l_buf ) );
                }

                bool write( connection_ptr p_con,
                            const message_ptr p_message ) {
                        
                    if ( ::write( p_con->socket( ),
                                  p_message->c_str( ),
                                  p_message->size( ) ) < 0 ) {
                        return false;
                    }
                    return true;
                }
            };

            typedef std::shared_ptr<message_io> message_io_ptr;

            struct message_io_factory {
                message_io_ptr create( ) {
                    return message_io_ptr( new message_io( ) );
                }
            };

            typedef std::shared_ptr<message_io_factory> message_io_factory_ptr;
        };


        struct translator_types {

            typedef message_types::message message;
            typedef message_types::message_ptr message_ptr;

            typedef app_data_types::app_data app_data;
            typedef app_data_types::app_data_ptr app_data_ptr;
                
            struct translator {
                app_data_ptr to_app_data( const message_ptr p_message ) {
                    return p_message;
                }

                message_ptr to_message( const app_data_ptr p_app_data ) {
                    return p_app_data;
                }
            };

            typedef std::shared_ptr<translator> translator_ptr;

            struct translator_factory {
                translator_ptr create( ) {
                    return translator_ptr( new translator( ) );
                }
            };

            typedef std::shared_ptr<translator_factory> translator_factory_ptr;
        };
            
        struct application_types {

            typedef connection_types::connection_ptr connection_ptr;
            typedef message_io_types::message_io_ptr message_io_ptr;
            typedef app_data_types::app_data_ptr app_data_ptr;
            typedef translator_types::translator_factory_ptr
            translator_factory_ptr; 
            typedef translator_types::translator translator;
            typedef translator_types::translator_ptr translator_ptr;

            struct application {
	  
                application( translator_ptr p_translator )
                    : m_translator( p_translator ) {}
                    
                void operator( )( const app_data_ptr p_app_data,
                                  connection_ptr p_con,
                                  message_io_ptr p_message_io ) {
                    message_types::message_ptr l_message =
                        m_translator->to_message( p_app_data );
                        
                    p_message_io->write( p_con, l_message );
                }

            private:
                translator_ptr m_translator;
            };

            typedef std::shared_ptr<application> application_ptr;

            struct application_factory {

                application_factory( translator_factory_ptr p_translator_factory )
                    : m_translator_factory( p_translator_factory ) {}

                application_ptr create( ) {
                    application_ptr l_rc(
                        new application( m_translator_factory->create( ) ) ); 
                    return l_rc;
                }                    
	  
            private:
                translator_factory_ptr m_translator_factory;
            };

            typedef std::shared_ptr<application_factory> application_factory_ptr;
        };

        struct timeout_types {
            typedef int16_t timeout;
            typedef timeout * timeout_ptr;
            struct timeout_factory {
                timeout_ptr create( ) {
                    return timeout_ptr();
                }
            };
            typedef timeout_factory * timeout_factory_ptr;
        };
	    
        struct concurrence_types {

            /** no concurrence */
            struct concurrence {

                typedef command::processor<message_types,
                                           connection_types,
                                           app_data_types,
                                           translator_types,
                                           application_types,
                                           timeout_types,
                                           message_io_types> processor;

                void submit( processor & p_processor ) { 
                    p_processor( );
                }
            };

            typedef std::shared_ptr<concurrence> concurrence_ptr;

            struct concurrence_factory {
                concurrence_ptr create( ) {
                    return concurrence_ptr( new concurrence( ) );
                }
            };

            typedef std::shared_ptr<concurrence_factory> concurrence_factory_ptr;
        };

        /** dummy */
        typedef int16_t log_types;
        
            

        typedef translator_types::translator_factory translator_factory;
        typedef translator_types::translator_factory_ptr translator_factory_ptr;

        typedef connector_types::connector_factory connector_factory;
        typedef connector_types::connector_factory_ptr connector_factory_ptr;

        typedef concurrence_types::concurrence_factory concurrence_factory;
        typedef concurrence_types::concurrence_factory_ptr concurrence_factory_ptr;

        typedef application_types::application_factory application_factory; 
        typedef application_types::application_factory_ptr application_factory_ptr; 

        typedef connector_types::connector_factory connector_factory;
        typedef connector_types::connector_factory_ptr connector_factory_ptr;

        typedef timeout_types::timeout_factory timeout_factory;
        typedef timeout_types::timeout_factory_ptr timeout_factory_ptr;

        typedef message_io_types::message_io_factory message_io_factory;
        typedef message_io_types::message_io_factory_ptr message_io_factory_ptr;

        typedef receiver <
            message_types,
            connection_types,
            app_data_types,
            translator_types,
            concurrence_types,
            application_types,
            connector_types,
            timeout_types,
            message_io_types,
            log_types> echo_receiver;

    }
}
    
using namespace tenacitas::echo_server;
int main( ) {
    
    translator_factory_ptr
        l_translator_factory( new translator_factory( ) );  

    concurrence_factory_ptr
        l_concurrence_factory( new concurrence_factory( ) );

    application_factory_ptr
        l_application_factory(
            new application_factory( l_translator_factory ) );  

    connector_factory_ptr
        l_connector_factory( new connector_factory( "45001" ) );

    timeout_factory_ptr
        l_timeout_factory( new timeout_factory( ) );

    message_io_factory_ptr
        l_message_io_factory( new message_io_factory( ) );

    echo_receiver l_receiver( l_translator_factory,
                              l_concurrence_factory,
                              l_application_factory,
                              l_connector_factory,
                              l_timeout_factory,
                              l_message_io_factory );

                
    l_receiver.start_sync( );

}
        
